﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Audio;

namespace ERF {
    /// <summary>
    /// This class manages sounds.
    /// </summary>
    public class SoundHandler : GameComponent {
        private List<SoundEffect3D> sounds3D;


        /// <summary>
        /// Creates a new instance of SoundHandler.
        /// </summary>
        /// <param name="game">The game this instance belongs to.</param>
        /// <param name="xactProjectName">The name of the XACT project.</param>
        /// <param name="xactFileName">The name of the sound file.</param>
        /// <param name="contentPath">Filepath to content.</param>
        public SoundHandler(ERFGame game)
            : base(game) {

            this.sounds3D = new List<SoundEffect3D>();
            game.Services.AddService(typeof(SoundHandler), this);
        }

        public override void Update(GameTime gameTime) {
            this.Update3DSounds();

            base.Update(gameTime);
        }

        private void Update3DSounds()
        {
            for (int i = this.sounds3D.Count - 1; i >= 0; i--)
            {
                if (this.sounds3D[i].HasExpired)
                {
                    this.sounds3D.RemoveAt(i);
                }
                else
                {
                    this.sounds3D[i].Update3DPositions();
                }
            }
        }

        /// <summary>
        /// Plays a 3D sound using one GameObject as emitter and another as listener.
        /// </summary>
        /// <param name="sound">The sound to play.</param>
        /// <param name="emitter">The emitting GameObject.</param>
        /// <param name="listener">The listening GameObject.</param>
        public void Play3DSound(SoundEffectInstance sound, GameObject emitter, GameObject listener)
        {
            SoundEffect3D soundEffect = new SoundEffect3D(sound, emitter, listener);
            this.sounds3D.Add(soundEffect);
        }

        /// <summary>
        /// Represents a 3D soundeffect.
        /// </summary>
        private class SoundEffect3D 
        {
            private GameObject emitterObject;
            private GameObject listenerObject;
            private SoundEffectInstance sound;
            private AudioEmitter audioEmitter;
            private AudioListener audioListener;

            /// <summary>
            /// Gets a bool denoting if the sound has stopped or if it has been disposed.
            /// </summary>
            public bool HasExpired
            {
                get { return (this.sound == null || this.sound.IsDisposed || this.sound.State == SoundState.Stopped); }
            }

            /// <summary>
            /// Creates a new instance of SoundEffect3D.
            /// </summary>
            /// <param name="sound">The sound that this instance will represent.</param>
            /// <param name="emitter">The GameObject that emitts this sound.</param>
            /// <param name="listener">The GameObject that listens to this sound.</param>
            public SoundEffect3D(SoundEffectInstance sound, GameObject emitter, GameObject listener)
            {
                this.sound = sound;
                this.emitterObject = emitter;
                this.listenerObject = listener;
                this.audioEmitter = new AudioEmitter();
                this.audioListener = new AudioListener();

                this.Update3DPositions();
                this.sound.Play();
            }

            /// <summary>
            /// Updates the properties of the sound based on the position of the emitter and listener.
            /// </summary>
            public void Update3DPositions()
            {
                this.audioEmitter.Position = this.emitterObject.Position;
                if(this.emitterObject.World.Forward != Vector3.Zero)
                    this.audioEmitter.Forward = Vector3.Normalize(this.emitterObject.ForwardVector);

                this.audioListener.Position = this.listenerObject.Position;
                if(this.listenerObject.World.Forward != Vector3.Zero)
                    this.audioListener.Forward = Vector3.Normalize(this.listenerObject.ForwardVector);

                this.sound.Apply3D(this.audioListener, this.audioEmitter);
            }
        }
    }
}
